#pragma once

#include "Port.h"
#include "Route.h"

#include <map>
#include <string>

namespace nexus {
class Network;

class Index {
 public:
  Index() {
    root_ = std::make_shared<TrieNode>(0);
  }

  struct TrieNode {
    TrieNode(int level) : level(level) {
      left = right = nullptr;
      rule = nullptr;
    }
    int level;
    std::shared_ptr<Route> rule;
    std::shared_ptr<TrieNode> left, right;
  };

  void
  insert(std::shared_ptr<Route> rule) {
    insert(rule, root_);
  }

  void
  insert(std::shared_ptr<Route> rule, std::shared_ptr<TrieNode> node) {
    if (node->level == rule->prefixLength) {
      // if (node->rule != nullptr) {
      //   std::cout << "exist" << std::endl;
      //   std::cout << node->rule->addr << ":" << node->rule->prefixLength <<
      //   std::endl; std::cout << rule->addr << ":" << rule->prefixLength <<
      //   std::endl;
      // }
      node->rule = rule;
      return;
    }
    int level = node->level;
    if (rule->match[31 - level] == 0) {
      if (node->left == nullptr) {
        node->left = std::make_shared<TrieNode>(level + 1);
      }
      insert(rule, node->left);
    } else {
      if (node->right == nullptr) {
        node->right = std::make_shared<TrieNode>(level + 1);
      }
      insert(rule, node->right);
    }
  }

  void
  getOverlapSet(
      std::shared_ptr<Route> rule, std::vector<std::shared_ptr<Route>>& out) {
    getOverlapSet(rule, root_, out);
  }

  void
  getOverlapSet(
      std::shared_ptr<Route> rule,
      std::shared_ptr<TrieNode> node,
      std::vector<std::shared_ptr<Route>>& out) {
    if (node == nullptr) {
      return;
    }
    if (node->level > rule->prefixLength &&
        node->rule != nullptr) { // we only return rules with higher priorities
      out.push_back(node->rule);
    }
    if (node->level < rule->prefixLength) {
      if (rule->match[31 - node->level] == 0) {
        getOverlapSet(rule, node->left, out);
      } else {
        getOverlapSet(rule, node->right, out);
      }
    } else {
      getOverlapSet(rule, node->left, out);
      getOverlapSet(rule, node->right, out);
    }
  }

 private:
  std::shared_ptr<TrieNode> root_;
};

class Device {
 public:
  Device(std::string name);

  std::string&
  name() {
    return name_;
  }

  auto&
  ports() {
    return ports_;
  }

  bool
  hasPort(std::string name) {
    return ports_.count(name) != 0;
  }

  Port&
  getOrCreatePort(std::string name) {
    if (!hasPort(name)) {
      Port port = {name, nullptr, this};
      ports_.emplace(name, port);
    }
    return ports_[name];
  }

  void
  getOverlapSet(
      std::shared_ptr<Route> rule, std::vector<std::shared_ptr<Route>>& out) {
    index_.getOverlapSet(rule, out);
  }

  auto&
  eportToHs() {
    return eportToHs_;
  }

  auto&
  eportToHs(Port* eport) {
    return eportToHs_.at(eport);
  }

  auto&
  routes() {
    return routes_;
  }

  auto
  network() {
    return network_;
  }

  void
  network(Network* net) {
    network_ = net;
  }

  auto
  type() {
    return type_;
  }
  void
  type(std::string t) {
    type_ = t;
  }

  auto
  x() {
    return x_;
  }
  void
  x(int v) {
    x_ = v;
  }
  auto
  y() {
    return y_;
  }
  void
  y(int y) {
    y_ = y;
  }

 private:
  std::string name_;
  Network* network_;
  std::map<std::string, Port> ports_;
  std::vector<Route> routes_;
  std::map<Port*, HeaderSpace> eportToHs_;
  std::string type_;
  int x_;
  int y_;

  Index index_;
};

} // namespace nexus